#pragma once

using namespace std;
#include <boost\multiprecision\cpp_bin_float.hpp>
#include <ppl.h>
#include <mutex>
#include <algorithm>

using namespace concurrency;

typedef D2D1::ColorF star_color;

//typedef boost::multiprecision::mpf_float_100 sfloat;
//typedef boost::multiprecision::cpp_bin_float_100 sfloat;
typedef double sfloat;

struct vector2d
{
	sfloat x;
	sfloat y;
};


struct star
{
	sfloat r; // radius

	sfloat m; // mass

	vector2d p; // position

	vector2d v; // velocity

	star(sfloat const & r, sfloat const & m, vector2d const & p, vector2d const & v)
		: r(r), m(m), p(p), v(v)
	{
	}

	star()
	{
	}
};

double getdouble(const sfloat& x)
{
	return static_cast<double>(x);
}

class star_system
{
public:
	star_system(double update_interval) :
		m_update_interval(update_interval)
	{
		LARGE_INTEGER freq;
		VERIFY(QueryPerformanceFrequency(&freq));
		m_freq = freq.QuadPart;
	}

	void add_star(const star& star)
	{
		m_stars.push_back(star);
	}

	void clear()
	{
		m_stars.clear();
	}

	void update()
	{
		#ifdef _DEBUG
		report_fps();
		#endif
		const sfloat& t = m_update_interval;

		for (auto & s1 : m_stars)
		{
			// star velocity
			// F = G * m1 * m2 / r^2
			// F has a direction: 
			sfloat Fdx = 0;
			sfloat Fdy = 0;

			sfloat Gm1 = G;		// G*s1.m
			sfloat ttm = t*t;	// t*t/s1.m
			mutex lock;

			for_each(cbegin(m_stars), cend(m_stars), [&](const star& s2){
			/*parallel_for_each(cbegin(m_stars), cend(m_stars), [&](const star& s2) {*/
				if (&s1 != &s2)
				{
					sfloat rx = s2.p.x - s1.p.x;
					sfloat ry = s2.p.y - s1.p.y;
					sfloat rr = rx*rx + ry*ry;
					sfloat r = sqrt(rr);

					sfloat f = Gm1*s2.m / rr;
					sfloat fdx = f*rx / r;
					sfloat fdy = f*ry / r;

					TRACE(L"%f %f %f\n", f, ry, r);
					/*lock.lock();*/
					Fdx += fdx;
					Fdy += fdy;
					/*lock.unlock();*/
				}
			});
			
			// Ft = ma	-> a = Ft/m
			// v = at	-> v = Ftt/m
			sfloat dvx = Fdx * ttm;
			sfloat dvy = Fdy * ttm;

			s1.v.x += dvx;
			s1.v.y += dvy;
		}

		for (auto & s : m_stars)
		{
			// star position change simply by it's velocity.
			s.p.x += s.v.x * t;
			s.p.y += s.v.y * t;
		}
	}

	const vector<star>& stars()
	{
		return m_stars;
	}

private:

	void report_fps()
	{
		LARGE_INTEGER qpc;
		VERIFY(QueryPerformanceCounter(&qpc));
		double ellapsed = 1.0 * (qpc.QuadPart - m_lastQpc) / m_freq;

		++m_frames;

		if (ellapsed >= 1.0)
		{
			TRACE(L"star system fps: %.2f\n", m_frames / ellapsed);
			m_lastQpc = qpc.QuadPart;
			m_frames = 0;
		}
	}
	
	long long m_lastQpc = 0;

	long long m_freq;

	int m_frames = 0;

	// collection of stars.
	vector<star> m_stars;

	// system values.
	const sfloat m_update_interval;

	/*const double G = 6.67384e-11;*/
	const sfloat G = 100;
};